﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace DefensiveShield
{
    public static partial class OS
    {
        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- CHECK FILES INTEGRITY ------
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция проверки целостности файлов
             * 
             * Файлы, которые были указаны, как файлы, которым необходима проверка целостности поочереди проверяются на наличие в них изменений с момента регистрации.
             * При этом проверяются лишь те файлы, к которым у пользователя есть доступ на чтение в соответствии с политиками разграничения прав.
             * 
             * Параметр по умолчанию указывает, нужно ли выполнять полную проверку целостности в обход ограничений на доступ
             * 
            @return ErrorCodes - код ошибки при его наличии
        */
        public static ErrorCodes CheckFilesIntegrity(OsSession session, bool fullCheck = false)
        {
            try
            {
                foreach (KeyValuePair<string, IntegrityState> fileNameHashPair in FileIntegrity)
                {
                    string fileName = fileNameHashPair.Key;
                    try
                    {
                        var hash = fileNameHashPair.Value.HashValue;

                        // Если файла не существует, он считается испорченным
                        if (!File.Exists(fileName))
                        {
                            FileIntegrity[fileName].IntegrityValue = Integrity.Corrupted;
                        }
                        // Если есть права на чтение - проверить
                        // Если файл не зарегистрирован в системе - то каждый имеет к нему доступ и может проверить его целостность
                        // Ленивые вычисления - важны !!!
                        else if (fullCheck ||
                                 !Files.ContainsKey(fileName) ||
                                 session.UserName == AdministratorName ||
                                    /* TIP: This must be temporary while Integrity Check is available only for admin in Graphon */
                                 (CheckMandatoryAccessPermission(session, fileName) == ErrorCodes.NoError &&
                                  GetDiscretionalAccessPermission(session, fileName) >= Rights.Read))
                        {
                            string fileContents = File.ReadAllText(fileName),
                                fileHash = Hash(fileContents);

                            if (fileHash != hash)
                                FileIntegrity[fileName].IntegrityValue = Integrity.Corrupted;
                            else
                                FileIntegrity[fileName].IntegrityValue = Integrity.Valid;
                        }
                    }
                    catch (Exception)
                    {
                        FileIntegrity[fileName].IntegrityValue = Integrity.ErrorAppeared;
                    }
                }

                LogMessage(session.UserName, "File integerity check successfully completed");
                return ErrorCodes.NoError;
            }
            catch (Exception e)
            {
                LogMessage(session.UserName, "Unexpected exception while checking files integrity: " + e.Message);
                return ErrorCodes.ErrorWhileCheckingFilesIntegrity;
            }
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- CHECK FILES INTEGRITY ------
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция пересчёта контрольных сумм файлов, добавленных в список отслеживания целостности файлов
             * 
             * Если файл не существует, то он будет удалён из списка проверки целостности, в случае, если пересчёт производится администратором, иначе он будет пропущен.
             * 
             * Пересчёт происходит лишь если у пользователя есть права доступа на чтение и запись, в соответствии с моделями разграничения доступа.
             * 
            @return ErrorCodes - код ошибки при её наличии
        */
        public static ErrorCodes RecalculateFilesIntegrityControlSums(OsSession session)
        {
            try
            {
                foreach (KeyValuePair<string, IntegrityState> fileNameHashPair in FileIntegrity)
                {
                    string fileName = fileNameHashPair.Key;

                    // Если файла не существует, попробовать удалить (не получится - просто пропускаем)
                    if (!File.Exists(fileName))
                    {
                        RemoveFileFromIntegrityChecker(session, fileName);
                    }
                    // Если есть права на чтение и запись, посчитать новый хэш, иначе просто пропустить
                    else if (CheckMandatoryAccessPermission(session, fileName) == ErrorCodes.NoError &&
                             GetDiscretionalAccessPermission(session, fileName) == Rights.ReadWrite)
                    {
                        string fileContents = File.ReadAllText(fileName),
                            fileHash = Hash(fileContents);

                        if (fileHash == null)
                        {
                            LogMessage(session.UserName, "Failed to recalculate control sum", fileName);
                            return ErrorCodes.NoError;
                        }

                        FileIntegrity[fileName].HashValue = fileHash;
                        FileIntegrity[fileName].IntegrityValue = Integrity.Valid;
                    }
                }

                DateLastIntegritySnapshot = DateTime.Now;

                LogMessage(session.UserName, "Control sums successfully recalculated");
                return ErrorCodes.NoError;
            }
            catch (Exception e)
            {
                LogMessage(session.UserName, "Unexpected exception while recalculating control sums for files in integrity checker: " + e.Message);
                return ErrorCodes.UnknownError;
            }
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- ADD FILE TO INTEGRITY CHECKER
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция добавления файла к списку файлов, для которых возможна проверка целостности
             * 
             * В случае, если файл не существует, или файл уже зарегитсрирован, или невозможно просчитать хеш этого файла - возвращается код ошибки.
             * 
            @return код ошибки, при её наличии
        */
        public static ErrorCodes AddFileToIntegrityChecker(OsSession session, string fileName)
        {
            try
            {
                // Если файла не существует, вернуть ошибку
                if (!File.Exists(fileName))
                {
                    LogMessage(session.UserName, "Cannot add file to integrity checker as it does not exist.", fileName);
                    return ErrorCodes.IntegrityErrorFileDoesNotExist;
                }

                if (FileIntegrity.ContainsKey(fileName))
                {
                    LogMessage(session.UserName, "Cannot add file to integrity checker as it is already added.",
                        fileName);
                    return ErrorCodes.IntegrityErrorFileAlreadyIsChecking;
                }

                string fileContents = File.ReadAllText(fileName),
                    fileHash = Hash(fileContents);

                if (fileHash == null)
                {
                    LogMessage(session.UserName, "Failed to calculate control sum for newly added file", fileName);
                    return ErrorCodes.ErrorInTakingHashInIntegrityAdder;
                }
                FileIntegrity[fileName] = new IntegrityState(){HashValue = fileHash, IntegrityValue = Integrity.Valid};

                LogMessage(session.UserName, "File successfully added to integrity checker", fileName);
                return ErrorCodes.NoError;
            }
            catch (Exception e)
            {
                LogMessage(session.UserName, "Error while adding file to integrity checker: " + e.Message, fileName);
                return ErrorCodes.UnknownError;
            }
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- REMOVE FILE FROM INTEGRITY -
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция удаления файла из списка файлов, для которых возможна проверка целостности
             * 
             * Файл, который не зарегистрирован в системе может быть удалён лишь администратором.
             * Для остальных файлов - проверяется, имеет ли пользователь к нему доступ на чтение и запись, и лишь в случае успеха удаляется из списка проверки целостности.
             * 
            @return код ошибки, при её наличии
        */
        public static ErrorCodes RemoveFileFromIntegrityChecker(OsSession session, string fileName)
        {
            try
            {
                // Если файл не зарегистрирован в системе, то удалить его может только администратор.
                // Если зарегистирован, для него можно проверить права обычным образом
                bool isRegistered = Files.ContainsKey(fileName),
                    canRemoveUnregistered = !isRegistered && session.UserName == AdministratorName,
                    canRemoveRegistered = isRegistered &&
                                          CheckMandatoryAccessPermission(session, fileName) == ErrorCodes.NoError &&
                                          GetDiscretionalAccessPermission(session, fileName) == Rights.ReadWrite;

                if (canRemoveUnregistered || canRemoveRegistered)
                {
                    // Если файла не существует, ничего не произойдет.
                    FileIntegrity.Remove(fileName);

                    LogMessage(session.UserName, "File successfully removed from integrity checker", fileName);
                    return ErrorCodes.NoError;
                }
                else
                {
                    LogMessage(session.UserName, "ACCESS VIOLATION: not enough rights to remove file from integrity checker.", fileName);

                    return ErrorCodes.NotEnoughPermissionsToDeleteFileFromIntegrityCheck;
                }
            }
            catch (Exception e)
            {
                LogMessage(session.UserName, "Unexpected exception while removing file from integrity checker: " + e.Message, fileName);
                return ErrorCodes.UnknownError;
            }
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- REMOVE FILE FROM INTEGRITY -
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция возвращающая список файлов, добавленных для проверки целостности
             * 
             * Функция возвращает файлы, которые зарегистрированны в системе, и к которым у пользователя есть доступ на чтение в соответствии с моделями разграничения доступа.
             * 
            @return List<string> - список имён файлов
            @return null - в случае ошибки
        */
        public static Dictionary<string, IntegrityState> GetIntegrityCheckFiles(OsSession session)
        {
            try
            {
                var files = new Dictionary<string, IntegrityState>();
                foreach (KeyValuePair<string, IntegrityState> fileNameHashPair in FileIntegrity)
                {
                    if (!Files.ContainsKey(fileNameHashPair.Key) ||
                        (CheckMandatoryAccessPermission(session, fileNameHashPair.Key) == ErrorCodes.NoError &&
                        GetDiscretionalAccessPermission(session, fileNameHashPair.Key) >= Rights.Read) ||
                        session.UserName == AdministratorName /* TIP: This must be temporary while Integrity Check is available only for admin in Graphon */)
                        files[fileNameHashPair.Key] = fileNameHashPair.Value;
                }

                LogMessage(session.UserName, "List of files for integruty check successfully retrieved");
                return files;
            }
            catch (Exception e)
            {
                LogMessage(session.UserName, "Unexpected exception while getting list of files checked for integrity: " + e.Message);
                return null;
            }
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- ALL INTEGIRTY CHECK VALID --
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция возвращающая ошибку если хотя бы для одного из файлов нарушена целостность
             * 
            @return ErrorCodes - возвращает отсутствие ошибки, если целостность для всех файлов не нарушена, либо ошибку, если хоть один файл испорчен
        */
        public static ErrorCodes AllIntegrityCheckValid(OsSession session)
        {
            CheckFilesIntegrity(session, true);
            if (FileIntegrity.Any(fileNameHashPair => fileNameHashPair.Value.IntegrityValue != Integrity.Valid))
            {
                return ErrorCodes.IntegrityCorrupt;
            }
            return ErrorCodes.NoError;
        }
    }
}
